﻿using Common;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Text;
using System.Threading;

namespace Bussiness
{
    public class Aeltalib
    {
        public string DevName { get; set; }
        SerialPortlib _Port = new SerialPortlib();

        /// <summary>
        /// 开启串口
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public bool Open(string args)
        {
            return _Port.Open(args);
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public bool Close(object args)
        {
            if (_Port == null) return true;
            _Port.Close(string.Empty);
            return true;
        }

        /// <summary>
        /// gfvfgt
        /// </summary>
        /// <param name="regAddr"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public ushort[] ReadRegister(byte cmdCode, string regAddr, int length)
        {
            try
            {
                if (this._Port == null) return null;
                string addr = regAddr.Substring(0, 1);
                string reg = regAddr.Substring(1);
                if (!Enum.IsDefined(typeof(起始地址), addr)) return null;
                int ireg = 0;
                if (!int.TryParse(reg, out ireg)) return null;
                起始地址 s = (起始地址)Enum.Parse(typeof(起始地址), addr);
                ushort v = (ushort)length;
                byte[] buffer = new byte[2];
                buffer[0] = (byte)(v / 256);
                buffer[1] = (byte)(v % 256);
                byte[] dta = 发送指令(this._Port, 从站地址.PLC1, (命令码)cmdCode, s, ireg, buffer);

                //3A-30-31-30-33-30-32-30-30-30-30-46-41-0D-0A
                if (dta != null && dta.Length % 2 == 0)
                {
                    LOG.Write(this.DevName + "   ReadRegister：" + BitConverter.ToString(dta));
                    ushort[] res = new ushort[dta.Length / 2];
                    for (int i = 0; i < dta.Length; i += 2)
                    {
                        byte[] bg = new byte[2];
                        bg[0] = dta[i + 1];
                        bg[1] = dta[i];
                        res[i / 2] = BitConverter.ToUInt16(bg, 0);
                    }

                    return res;
                }
                else
                {
                    LOG.Write(this.DevName + "   ReadRegister：" + BitConverter.ToString(dta) + " 长度=" + dta.Length + " 长度不是偶数");
                    return null;
                }
            }
            catch (Exception ex)
            {
                LOG.Write(this.DevName + "   ReadRegister Error：" + ex.Message + ex.StackTrace);
                return null;
            }
        }

        /// <summary>
        /// 台达PLC读浮点数
        /// </summary>
        /// <param name="regAddr"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public ushort[] ReadRegisterDB(byte cmdCode, string regAddr, int length, byte devID = 0x01)
        {
            try
            {
                if (this._Port == null) return null;
                string addr = regAddr.Substring(0, 1);
                string reg = regAddr.Substring(1);
                if (!Enum.IsDefined(typeof(起始地址), addr)) return null;
                int ireg = 0;
                if (!int.TryParse(reg, out ireg)) return null;
                起始地址 s = (起始地址)Enum.Parse(typeof(起始地址), addr);
                ushort v = (ushort)length;
                byte[] buffer = new byte[2];
                buffer[0] = (byte)(v / 256);
                buffer[1] = (byte)(v % 256);
                byte[] dta = 发送指令(this._Port, (从站地址)devID, (命令码)cmdCode, s, ireg, buffer);

                if (dta != null && dta.Length % 2 == 0)
                {
                    LOG.Write(this.DevName + "   ReadRegister：" + BitConverter.ToString(dta));
                    ushort[] res = new ushort[dta.Length / 2];
                    for (int i = 0; i < dta.Length; i += 2)
                    {
                        byte[] bg = new byte[2];
                        bg[0] = dta[i + 1];
                        bg[1] = dta[i];

                        res[i / 2] = BitConverter.ToUInt16(bg, 0);
                    }
                    return res;
                }
                else
                {
                    LOG.Write(this.DevName + "   ReadRegister：" + null);
                    return null;
                }
            }
            catch (Exception ex)
            {
                LOG.Write(this.DevName + "   ReadRegister Error：" + ex.Message + ex.StackTrace);
                return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="regAddr"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public byte ReadIO(string regAddr)
        {
            try
            {
                if (this._Port == null) return 0x00;
                string addr = regAddr.Substring(0, 1);
                string reg = regAddr.Substring(1);
                if (!Enum.IsDefined(typeof(起始地址), addr)) return 0x00;
                int ireg = 0;
                if (!int.TryParse(reg, out ireg)) return 0x00;
                int index = ireg % 8;
                ireg = ireg / 8;
                ireg *= 8;
                起始地址 s = (起始地址)Enum.Parse(typeof(起始地址), addr);
                byte[] buffer = new byte[2];
                buffer[0] = 0x00;
                buffer[1] = 0x08;
                byte[] dta = 发送指令(this._Port, 从站地址.PLC1, 命令码.读节点状态, s, ireg, buffer);

                if (dta != null && dta.Length == 1)
                {
                    LOG.Write(this.DevName + "   ReadIO：" + BitConverter.ToString(dta));
                    return dta[0];

                }
                else
                {
                    LOG.Write(this.DevName + "   ReadIO：" + null);
                    return 0xFF;
                }

            }
            catch (Exception ex)
            {
                LOG.Write(this.DevName + "   ReadIO Error：" + ex.Message + ex.StackTrace);
                return 0xff;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="regAddr"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public bool SetRegister(byte regType, string regAddr, ushort val)
        {
            try
            {
                if (this._Port == null) return false;
                string addr = regAddr.Substring(0, 1);
                string reg = regAddr.Substring(1);
                if (!Enum.IsDefined(typeof(起始地址), addr)) return false;
                int ireg = 0;
                if (!int.TryParse(reg, out ireg)) return false;
                起始地址 s = (起始地址)Enum.Parse(typeof(起始地址), addr);
                byte[] buffer = BitConverter.GetBytes(val);
                byte vvm = buffer[0];
                buffer[0] = buffer[1];
                buffer[1] = vvm;
                byte[] dta = 发送指令(this._Port, 从站地址.PLC1, (命令码)regType, s, ireg, buffer);
                return dta != null;
            }
            catch (Exception ex)
            {
                LOG.Write(this.DevName + "   SetRegister Error：" + ex.Message + ex.StackTrace);
                return false;
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="startRegAddr"></param>
        /// <param name="length"></param>
        /// <param name="vals"></param>
        /// <returns></returns>
        public bool SetRegisters(string regType, string startRegAddr, int length, ushort[] vals)
        {
            throw new NotImplementedException();
        }


        public float[] ReadRegisterF(byte regType, string regAddr, int length)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 封装的最上层的 PLC串口发送方法；返回 想要的数据 或者 状态；
        /// </summary>
        /// <param name="串口"></param>
        /// <param name="PLC地址"></param>
        /// <param name="功能码"></param>
        /// <param name="寄存器"></param>
        /// <param name="寄存器号"></param>
        /// <param name="数据"></param>
        /// <param name="数据字节数"></param>
        /// <returns></returns>
        static public byte[] 发送指令(SerialPortlib 串口, 从站地址 PLC地址, 命令码 功能码, 起始地址 寄存器 = 起始地址.D, int 寄存器号 = 0, byte[] 数据 = null, int 数据字节数 = 0)
        {
            byte 发送状态 = 0x01;
            byte[] 返回结果 = null;

            for (int i = 0; i < 3; i++)
            {
                发送状态 = 发指令包(串口, PLC地址, 功能码, 寄存器, 寄存器号, 数据);
                LOG.Write("PLC   发送指令：" + 发送状态.ToString());
                if (发送状态 == 0x00)       // 发送成功，读取数据
                {
                    //Thread.Sleep(500);       // 延时等待数据返回
                    返回结果 = 接收数据(串口);
                    if (返回结果 == null)
                    {
                        LOG.Write("PLC   发送指令： 返回结果 null");
                    }
                    else
                    {
                        LOG.Write("PLC   发送指令：" + BitConverter.ToString(返回结果));
                    }

                    return 返回结果;
                }
            }
            return null;    // 循环3次均发送失败
        }

        /// <summary>
        /// 封装的PLC串口发送指令：
        /// </summary>
        /// <param name="PLC地址"> PLC从站地址 </param>
        /// <param name="功能码"> PLC操作 对应的命令码 </param>
        /// <param name="寄存器"> PLC装置基础地址：对应寄存器类型 </param>
        /// <param name="寄存器号"> 装置偏移地址：寄存器编号 </param>
        /// <param name="数据">发送的数据：字节数、寄存器值，等； </param>
        /// <returns> 发送结果：成功，0；失败，1； </returns>
        static private byte 发指令包(SerialPortlib 串口, 从站地址 PLC地址, 命令码 功能码, 起始地址 寄存器 = 起始地址.D, int 寄存器号 = 0, byte[] 数据 = null)
        {
            try
            {
                while (串口.BytesCanRead > 0)      // 发送之前，先清空接收缓冲区
                {
                    串口.ReadData();
                }

                byte[] 包 = 字节数组转ASCII(组指令包(PLC地址, 功能码, 寄存器, 寄存器号, 数据));
                string debugstr = BitConverter.ToString(包);
                串口.WriteData(包);       // 发送最终数据
                return 0x00;    // 发送成功
            }
            catch (Exception e)
            {
                return 0x01;    // 发送失败
            }
        }

        /// <summary>
        /// 读串口缓冲区，接收返回的数据；指定接收的数据字节数；虽然指定也没用，主要还是依据结束字符和超时，来判断。
        /// </summary>
        /// <param name="串口"> 指定对应的串口 </param>
        /// <param name="数据字节数"> 想要获得的数据字节数，可以不指定，收到多少返回多少 </param>
        /// <param name="等待时间"> 接收等待的时间，超时即判定为通信出错 </param>
        /// <returns></returns>
        static private byte[] 接收数据(SerialPortlib 串口, int 数据字节数 = 0, int 等待时间 = 500)
        {
            List<byte> buffer = new List<byte>();
            int index = 0;
            while (true)    // 等待一定时间  看看串口是否有响应；但是如果已经接收到足够/指定的数据，则直接停止
            {
                //XXX：此处时间间隔太小的话，会读不到后续的数据，造成后续数据积压
                Thread.Sleep(30);//此时间必须很小，不然会出现数据乱的现象
                index++;
                if (index >= 30) //OOO:扩展到600毫秒
                {
                    LOG.Write("PLC   串口 no data receive");
                    break;
                    //XXX：60毫秒，读不到数据就会退出，实际数据有点延迟过来，超过60秒
                }

                LOG.Write("PLC   串口.BytesCanRead：" + 串口.BytesCanRead);
                if (串口.BytesCanRead > 0)         // 如果有接收到数据
                {
                    Byte[] bufferb = 串口.ReadData();
                    LOG.Write("PLC   串口数据：" + BitConverter.ToString(bufferb));
                    if (bufferb == null) continue;
                    LOG.Write("PLC   串口.bufferb：" + bufferb.Length);
                    buffer.AddRange(bufferb);
                    if (buffer.Count <= 3) continue;
                    for (int i = 0; i < buffer.Count; i++)
                    {
                        if (buffer[0] != 0x3a)
                            buffer.RemoveAt(0);
                    }
                    if (buffer[buffer.Count - 1] != 0x0a)
                        continue;
                    else
                        break;
                }
                //return 接收的数据.Skip(0).Take(i + 1).ToArray();     // 不知道为什么 Skip 用不了
                //return 解指令包(接收的数据, i + 1);      // 索引为 i；所以字节数为 i+1
            }

            byte[] dta = buffer.ToArray();
            LOG.Write("PLC   接收数据：" + BitConverter.ToString(dta));
            //3A-30-B1-30-33-30-B2-30-30-33-C3-42-C5-8D-0A
            //dta = new byte[15];
            //dta[0]= 0x3A;
            //dta[1] = 0x30;
            //dta[2] = 0xB1;
            //dta[3] = 0x30;
            //dta[4] = 0x33;
            //dta[5] = 0x30;
            //dta[6] = 0xB2;
            //dta[7] = 0x30;
            //dta[8] = 0x30;
            //dta[9] = 0x33;
            //dta[10] = 0xC3;
            //dta[11] = 0x42;
            //dta[12] = 0xC5;
            //dta[13] = 0x8D;
            //dta[14] = 0x0A;
            byte[] ss = 解指令包(dta);
            return ss;
        }


        /// <summary>
        /// 将16进制的指令数组 转为最终需要发送的 ASCII码值数组
        /// </summary>
        /// <param name="指令包"> 不含起始字符、但含校验码的 指令数组 </param>
        /// <returns>准备发送的 最终数据 </returns>
        static private byte[] 字节数组转ASCII(byte[] 指令包)
        {
            if (指令包 == null)     // 如果指令为空，则返回空
                return null;

            int 字节数 = 指令包.Length * 2 + 1 + 1 + 1;       // 起始位 1、停止位 1+1
            byte[] 发送数据 = new byte[字节数];

            string 指令字符串 = ":";         // 将指令数组转为 字符串，以便转为 ASCII码数组
            for (int i = 0; i < 指令包.Length; i++)
                指令字符串 += 指令包[i].ToString("X2");     // "X2"：将 16进制 转为 字符串
            指令字符串 += "10";

            发送数据 = Encoding.Default.GetBytes(指令字符串);    // 将字符串 转为 16进制数组
            发送数据[字节数 - 2] = 0x0D;                       // 两字节 停止码
            发送数据[字节数 - 1] = 0x0A;

            return 发送数据;
        }

        /// <summary>
        /// 将指令有效部分的 ASCII码数组 转为 16进制 字节数组
        /// </summary>
        /// <param name="原ASCII组"> 指令包去除 起始位=字符和结束字符 </param>
        /// <returns></returns>
        static private byte[] ASCII转字节数组(byte[] 原ASCII组)
        {
            string ASCII码 = Encoding.ASCII.GetString(原ASCII组);
            if (ASCII码.Length % 2 != 0)
                return null;            // 转换的长度不是2的整数倍，表明数据出错；返回 空

            ASCII码 = ASCII码.Split('\r')[0];     // 需要转义？？   之前此处会报错，因为后续接了一串 其它字符；以'\r\n'分隔；
            byte[] 返回结果 = new byte[ASCII码.Length / 2];
            try
            {
                for (int i = 0; i < 返回结果.Length; i++)
                    返回结果[i] = Convert.ToByte(ASCII码.Substring(i * 2, 2), 16);   // 将string 转换为16进制的 byte
            }
            catch (Exception ex)
            {

            }
            return 返回结果;
        }

        /// <summary>
        /// 根据16进制的指令数组 计算得到LCR校验和
        /// </summary>
        /// <param name="指令"> 不含起始字符 的 指令数组 </param>
        /// <param name="裁剪"> 裁剪掉指令数组的后 多少个 字节 </param>
        static private byte 计算校验和(byte[] 指令, int 裁剪 = 0)
        {
            if (指令 == null)
                return 0x00;
            byte 校验和 = 0x00;
            int 字节数 = 指令.Length - 裁剪;       // 实际需要计算校验和的字节数 
            for (int i = 0; i < 字节数; i++)
                校验和 += 指令[i];
            校验和 = (byte)(~校验和 + 1);         // 按位取反 再加1
            return 校验和;
        }

        /// <summary>
        /// 核对校验和，判断指令是否正确；指令不含 起止字符
        /// </summary>
        /// <param name="指令"> 最后一字节为校验和 </param>
        /// <returns></returns>
        static private bool 核对校验和(byte[] 指令)
        {
            if (指令 == null)
                return false;
            byte 校验和 = 0x00;
            for (int i = 0; i < 指令.Length - 1; i++)
                校验和 += 指令[i];
            校验和 = (byte)(~校验和 + 1);
            if (校验和 == 指令[指令.Length - 1])
                return true;
            else
                return false;
        }

        /// <summary>
        /// 指定 地址、功能码、寄存器、数据 等，打包为一个指令包
        /// </summary>
        /// <param name="PLC地址"></param>
        /// <param name="功能码"></param>
        /// <param name="寄存器"></param>
        /// <param name="寄存器号"></param>
        /// <param name="数据"></param>
        /// <returns></returns>
        static private byte[] 组指令包(从站地址 PLC地址, 命令码 功能码, 起始地址 寄存器 = 起始地址.D, int 寄存器号 = 0, byte[] 数据 = null)
        {
            if (数据 == null)
                return null;
            byte[] 指令包 = new byte[数据.Length + 5];       // 指令包长度：数据 + 1 + 1 + 2 + 1；加上校验和；
            int 装置地址 = (int)寄存器 + 寄存器号;         // 计算出装置/寄存器 地址
            指令包[0] = (byte)PLC地址;
            指令包[1] = (byte)功能码;
            指令包[2] = (byte)(装置地址 >> 8);         // 右移8位，取16位数据的髙字节
            指令包[3] = (byte)(装置地址 % 256);        // 取16位数据的低字节

            for (int i = 4; i < 指令包.Length - 1; i++)     // 最后一字节是校验和，需要 -1
                指令包[i] = 数据[i - 4];
            // 计算 指令包 的前几个字节的校验和
            指令包[指令包.Length - 1] = 计算校验和(指令包, 1);
            return 指令包;
        }

        /// <summary>
        /// 解析原始指令包；主要用于 对接收的PLC返回数据进行解析，得到想要的数据：16进制；
        /// </summary>
        /// <param name="原始指令包"> 串口接收的 带起止字符 的数组，末尾可能有多余的字节 </param>
        /// <param name="字节数"> 有意义的字节数 </param>
        /// <returns></returns>
        static private byte[] 解指令包(byte[] 原始指令包, int 字节数 = 0)
        {
            //3A-30-31-30-33-30-34-38-30-30-30-34-32-42-42-37
            LOG.Write("PLC   原始指令包---" + BitConverter.ToString(原始指令包));
            if (字节数 == 0)
                字节数 = 原始指令包.Length;
            //byte[] 返回结果 = null;
            if (字节数 <= 3)
            {
                LOG.Write("PLC   字节数 <= 3");
                return null;// 字节数 不大于 3，说明出错了。
            }

            byte[] 裁剪指令包 = new byte[字节数 - 3];       // 裁剪掉 1个起始字符、2个结束字符；

            for (int i = 0; i < 字节数 - 3; i++)
                裁剪指令包[i] = 原始指令包[i + 1];
            byte[] 字节指令 = ASCII转字节数组(裁剪指令包);        // 实际 16进制的指令
            LOG.Write("PLC   字节指令：" + BitConverter.ToString(字节指令));

            if (!核对校验和(字节指令))       // 校验和不正确，返回空
            {
                LOG.Write("PLC   核对校验和：false");
                return null;
            }

            if (字节指令[1] == (byte)命令码.异常码返回)         // 返回出现异常
            {
                byte[] 返回结果 = { 字节指令[2] };
                LOG.Write("PLC   返回结果：" + BitConverter.ToString(返回结果));
                return 返回结果;
            }
            if (字节指令[1] <= 0x03)         // 对读指令的返回
            {
                byte[] 返回结果 = new byte[字节指令[2]];
                for (int i = 0; i < 字节指令[2]; i++)
                    返回结果[i] = 字节指令[i + 3];          // 从第4个开始才是数据：i+3
                LOG.Write("PLC   返回结果：" + BitConverter.ToString(返回结果));
                return 返回结果;
            }
            byte[] 返回 = { 字节指令[1] };         // 非读指令，随便返回一个字节：命令码
            LOG.Write("PLC   返回：" + BitConverter.ToString(返回));
            return 返回;
            /*
            if (字节数 == 0)
                字节数 = 原始指令包.Length;
            //byte[] 返回结果 = null;
            if (字节数 <= 3)       // 字节数 不大于 3，说明出错了。
                return null;

            byte[] 裁剪指令包 = new byte[字节数 - 3];       // 裁剪掉 1个起始字符、2个结束字符；

            for (int i = 0; i < 字节数 - 3; i++)
                裁剪指令包[i] = 原始指令包[i + 1];
            byte[] 字节指令 = ASCII转字节数组(裁剪指令包);        // 实际 16进制的指令

            if (!核对校验和(字节指令))       // 校验和不正确，返回空
                return null;

            if (字节指令[1] == (byte)命令码.异常码返回)         // 返回出现异常
            {
                byte[] 返回结果 = { 字节指令[2] };
                return 返回结果;
            }
            if (字节指令[1] <= 0x03)         // 对读指令的返回
            {
                byte[] 返回结果 = new byte[字节指令[2]];
                for (int i = 0; i < 字节指令[2]; i++)
                    返回结果[i] = 字节指令[i + 3];          // 从第4个开始才是数据：i+3
                return 返回结果;
            }
            byte[] 返回 = { 字节指令[1] };         // 非读指令，随便返回一个字节：命令码
            return 返回;
            */
        }

        //// 默认为 使用串口1 读PLC1 的 D寄存器
        //// 读单个寄存器的值，16位 有符号的整型；结果为空时，返回 0：逻辑有点问题，该值可能会冲突（这是一个BUG）；
        //private int 读单寄存器的值(int 寄存器号)
        //{
        //    byte[] 数据 = { 0x00, 0x01 };
        //    int 值 = 0;
        //    byte[] 结果 = 串口通信.发送指令(串口1, 从站地址.PLC1, 命令码.读寄存器值, 起始地址.D, 寄存器号, 数据);
        //    if (结果 == null)
        //        值 = 0;
        //    else if (结果.Length == 2)
        //        值 = 获取有符号数(结果[0], 结果[1]); // 转换为 有符号的数
        //    return 值;
        //}

        #region 测试模块

        static public byte 发数据(byte[] 数据, SerialPort 串口)
        {
            try
            {
                int 单个数据 = 0;
                while (串口.BytesToRead > 0)      // 发送之前，先清空接收缓冲区
                    单个数据 = 串口.ReadByte();

                串口.Write(数据, 0, 数据.Length);       // 发送最终数据
            }
            catch // (Exception e)
            {
                return 0x01;    // 发送失败
            }
            return 0x00;    // 发送成功
        }

        static public byte[] 收数据(SerialPort 串口, int 数据字节数, int 等待时间 = 500)
        {
            //byte[] 接收的数据 = new byte[数据字节数];
            //int 单个数据 = 0;
            //int i = 0;      // 索引

            Single timeA = 1000 * DateTime.Now.Second + DateTime.Now.Millisecond;   // 当前时间，毫秒
            Single timeB = timeA;
            while (timeB <= timeA + 等待时间)    // 等待一定时间  看看串口是否有响应；但是如果已经接收到足够/指定的数据，则直接停止
            {
                timeB = 1000 * DateTime.Now.Second + DateTime.Now.Millisecond;
                if (timeB < timeA)         // 异常，应该不会出现这种情况
                    timeB += 60000;
                if (串口.BytesToRead > 0)         // 如果有接收到数据
                {
                    byte[] 接收的数据 = new byte[串口.BytesToRead];
                    串口.Read(接收的数据, 0, 接收的数据.Length);
                    return 接收的数据;
                    //单个数据 = 串口.ReadByte();
                    //接收的数据[i] = (byte)单个数据;

                    //// 接收到特定的结束字符之后就返回；i>1为了确保 i-1不出错；
                    //if (i > 1 && (i >= 数据字节数 -1 || (接收的数据[i - 1] == 0x0D && 接收的数据[i] == 0x0A)))    // 0x0D 0x0A
                    //    break;      // 跳出循环
                }
                //return 接收的数据.Skip(0).Take(i + 1).ToArray();     // 不知道为什么 Skip 用不了
                //return 接收的数据;      // 索引为 i；所以字节数为 i+1
            }
            // 串口无响应 或出错，返回 null；
            return null;
        }

        #endregion
    }

    // PLC采用ASCII通信模式，即将一个8bit的16进制数 分为两个ASCII码发送：如0x12-->0x01 0x02

    /// <summary>
    /// 从站地址：两个 ASCII 码；
    /// </summary>
    public enum 从站地址
    {
        Null = -1,     // 未指定正确地址
        PLC1 = 0x01,
        PLC2 = 0x02
    };

    /// <summary>
    /// 命令码：两个 ASCII 码；
    /// </summary>
    public enum 命令码
    {
        Null = -1,     // 未指定正确命令
        读非输入节点状态 = 0x01,
        读节点状态 = 0x02,
        读寄存器值 = 0x03,
        强制单节点状态 = 0x05,
        预设单寄存器值 = 0x06,
        强制多节点状态 = 0x0F,
        预设多寄存器值 = 0x10,
        报告从站地址 = 0x11,
        轮询时间内读写 = 0x17,
        异常码返回 = 0x81
    };

    /// <summary>
    /// 装置地址：即各个寄存器地址；4个 ASCII 码；
    /// </summary>
    public enum 起始地址
    {
        Null = -1,     // 未指定正确地址
        S = 0x0000,
        X = 0x0400,
        Y = 0x0500,
        T = 0x0600,
        M = 0x0800,
        C = 0x0E00,
        D = 0x1000

    };

    /// <summary>
    /// 异常码：两个 ASCII 码；
    /// </summary>
    public enum 异常码
    {
        Null = -1,     // 
        非法命令码 = 0x01,
        非法装置地址 = 0x02,
        非法装置值 = 0x03,
        校验和或指令出错 = 0x07
    };

    /// <summary>
    /// 节点置位/复位
    /// </summary>
    public enum 开关
    {
        Null = -1,
        开 = 0xFF00,
        关 = 0x0000
    }
}
